
import estructuras.CircularListQueue;
import estructuras.ArrayQueue;
import java.util.ArrayList;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 * Clase para la simulación de las operaciones en las filas de un banco
 * @author asus
 */
public class Banco {
    
    /**
     * Lista que contiene los eventos de entrada y salida de clientes para la simulación.
     */
    private ArrayList<Evento> eventos;
   
     /**
     * Fila 1 del banco. 
     */
    private CircularListQueue<Cliente> cola1;
    
    /**
     * Fila 2 del banco. 
     */
    private CircularListQueue<Cliente> cola2;
    
    /**
     * Fila 3 del banco. 
     */
    private CircularListQueue<Cliente> cola3;
    
    /**
     * Fila 4 del banco. 
     */
    private CircularListQueue<Cliente> cola4;
   
    /*
    private ArrayQueue<Cliente> cola1;
    private ArrayQueue<Cliente> cola2; 
    private ArrayQueue<Cliente> cola3; 
    private ArrayQueue<Cliente> cola4; 
    */
    
    /**
     * Contiene el total de tiempos para calcular el promedio.
     */
    private int tiempoTotal = 0;
    
    /**
     * Contiene el total de clientes para calcular el promedio.
     */
    private int numClientes = 0;
    
    /**
     * Referencia a la interfaz gráfica. 
     */
    private InterfazHoja interfaz;
    
    /**
     * Constructor de la clase banco.
     * @param gui interfaz gráfica del programa.
     */
    public Banco(InterfazHoja gui) {
        eventos = new ArrayList<Evento>();
        
        cola1 = new CircularListQueue();
        cola2 = new CircularListQueue();
        cola3 = new CircularListQueue();
        cola4 = new CircularListQueue();
        
        /*
        cola1 = new ArrayQueue(10);
        cola2 = new ArrayQueue(10);
        cola3 = new ArrayQueue(10);
        cola4 = new ArrayQueue(10);
        */
        
        interfaz = gui;
    }
    
    public Banco() {
        this(null);
    }
    
    /**
     * Agrega un evento a la lista de eventos.
     * @param evt parámetro a agregar.
     */
    public void agregarEvento(Evento evt) {
         eventos.add(evt);
    }
    
    /**
     * Devuelve los eventos actuales de la simulación.
     * @return lista de eventos de la simulación. 
     */
    public ArrayList darEventos() {
        return eventos;
    }
  
    /**
     * Ejecuta la simulación, evaluando y creando eventos de llegada y salida hasta que no existan más en la lista.
     * Post: Se despliega el promedio de tiempo de permanencia, si el número de clientes ingresados es mayor que 0.
     */
    public void simular() throws Exception { 
        sortEventos();
        while (eventos.size() > 0) {
            Evento current = eventos.remove(0);
            evaluarEntradas(current);
            sortEventos();
            evaluarSalidas(current); 
            sortEventos();
        }
        if (numClientes != 0) interfaz.desplegarInfo("El promedio es: " + (double)tiempoTotal/(double)numClientes + " minutos");
        
    }
    
    /**
     * Distribuye a los clientes de llegada en las colas disponibles, para la simulación.
     * @param evt evento actual que se evalúa en la simulación.
     */
    private void evaluarEntradas(Evento evt) throws Exception {
        // Coloca al cliente en la cola más corta para un evento de entrada
        Evento actual = evt;
        if (actual.darTipo().equals("ENTRADA")) {
            
            if (cola1.size() <= cola2.size() && cola1.size() <= cola3.size() && cola1.size() <= cola4.size() && cola1.size() < 10) {
                if (cola1.size() > 0) {
                    cola1.offer(actual.darCliente());
                } else {
                    Cliente c = actual.darCliente();
                    Evento e = new Evento("SALIDA1", c.darT1()+c.darTiempoEnCola()+c.darT2(), c);
                    cola1.offer(c);
                    eventos.add(e);
                }
                interfaz.desplegarInfo("Cliente (" + actual.darCliente().darT1() + ", " + actual.darCliente().darT2() + ") asignado a cola 1");
            } 
                    
            else if (cola2.size() <= cola3.size() && cola2.size() <= cola4.size() && cola2.size() <= cola1.size() && cola2.size() < 10) {
                if (cola2.size() > 0) {
                    cola2.offer(actual.darCliente());
                } else {
                    Cliente c = actual.darCliente();
                    Evento e = new Evento("SALIDA2", c.darT1()+c.darTiempoEnCola()+c.darT2(), c);
                    cola2.offer(c);
                    eventos.add(e);
                }
                interfaz.desplegarInfo("Cliente (" + actual.darCliente().darT1() + ", " + actual.darCliente().darT2() + ") asignado a cola 2");
            } 
                    
            else if (cola3.size() <= cola1.size() && cola3.size() <= cola2.size() && cola3.size() <= cola4.size() && cola3.size() < 10) {
                if (cola3.size() > 0) {
                     cola3.offer(actual.darCliente());
                } else {
                    Cliente c = actual.darCliente();
                    Evento e = new Evento("SALIDA3", c.darT1()+c.darTiempoEnCola()+c.darT2(), c);
                    cola3.offer(c);
                    eventos.add(e);
                }
                interfaz.desplegarInfo("Cliente (" + actual.darCliente().darT1() + ", " + actual.darCliente().darT2() + ") asignado a cola 3");
            } else if (cola4.size() < 10) {
                if (cola4.size() > 0) {
                    cola4.offer(actual.darCliente());
                } else {
                    Cliente c = actual.darCliente();
                    Evento e = new Evento("SALIDA4", c.darT1()+c.darTiempoEnCola()+c.darT2(), c);
                    cola4.offer(c);
                    eventos.add(e);
                }
                interfaz.desplegarInfo("Cliente (" + actual.darCliente().darT1() + ", " + actual.darCliente().darT2() + ") asignado a cola 4");
            }
            
            if (cola1.size() >= 10 && cola2.size() >= 10 && cola3.size() >= 10 && cola4.size() >= 10) {
                ArrayList nuevosEventos = new ArrayList();
                for (int i = 0; i < eventos.size(); i++) {
                    Evento e = eventos.get(i);
                    if (!e.darTipo().equals("ENTRADA")) {
                        nuevosEventos.add(i);
                    }
                }
                eventos = nuevosEventos;
                throw new Exception("Colas llenas. Se eliminará el resto de eventos de entrada.");
            }
         }
      
    }
    
    /**
     * Este método realiza un bubblesort a la lista de eventos para mantenerla ordenada de forma ascendente, en base al tiempo de inicio de cada evento.
     * Post: La lista de eventos queda ordenada ascendentemente en base al tiempo de inicio de cada evento. 
     */
    public void sortEventos() {
        int numSorted = 0; // number of values in order
        int index; // general index
        
        Evento[] data = eventos.toArray(new Evento[0]);
        while (numSorted < eventos.size()) {
            for (index = 1; index < eventos.size()-numSorted; index++) {
                if (data[index-1].darTiempoInicio() > data[index].darTiempoInicio() )
                swap(data,index-1,index);
            }
        numSorted++;
        }
        
        eventos = new ArrayList();
        for (int i = 0; i < data.length; i++) {
            eventos.add(data[i]);
        }  
    }
    
    /**
     * Método interno para el bubblesort
     * @param data
     * @param i
     * @param j 
     */
    private void swap(Evento data[], int i, int j) {
        Evento temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }
    
    /**
     * Retira a los clientes de las colas para la simulación. 
     * @param actual 
     */
    private void evaluarSalidas(Evento actual) {
        Cliente saliente = actual.darCliente();
        int tiempo = saliente.darT2();
        if (actual.darTipo().equals("SALIDA1")) {
            for (int i = 0; i < cola1.size(); i++) {
                Cliente c = cola1.poll();
                c.sumarTiempoCola(tiempo);
                cola1.offer(c);
            }
            tiempoTotal += cola1.poll().darTiempoEnCola();
            numClientes++;
            Cliente proximo = cola1.peek();
            if (proximo != null) eventos.add(new Evento("SALIDA1", proximo.darT1()+proximo.darTiempoEnCola()+proximo.darT2(), proximo));
        } 
        
        else if (actual.darTipo().equals("SALIDA2")) {
            for (int i = 0; i < cola2.size(); i++) {
                Cliente c = cola2.poll();
                c.sumarTiempoCola(tiempo);
                cola2.offer(c);
            }
            tiempoTotal += cola2.poll().darTiempoEnCola();
            numClientes++;
            Cliente proximo = cola2.peek();
            if (proximo != null) eventos.add(new Evento("SALIDA2", proximo.darT1()+proximo.darTiempoEnCola()+proximo.darT2(), proximo));
        } 
        
        else if (actual.darTipo().equals("SALIDA3")) {
            for (int i = 0; i < cola3.size(); i++) {
                Cliente c = cola3.poll();
                c.sumarTiempoCola(tiempo);
                cola3.offer(c);
            }
            tiempoTotal += cola3.poll().darTiempoEnCola();
            numClientes++;
            Cliente proximo = cola3.peek();
            if (proximo != null) eventos.add(new Evento("SALIDA3", proximo.darT1()+proximo.darTiempoEnCola()+proximo.darT2(), proximo));
        } 
        
        else if (actual.darTipo().equals("SALIDA4")) {
            for (int i = 0; i < cola4.size(); i++) {
                Cliente c = cola4.poll();
                c.sumarTiempoCola(tiempo);
                cola4.offer(c);
            }
            tiempoTotal += cola4.poll().darTiempoEnCola();
            numClientes++;
            Cliente proximo = cola4.peek();
            if (proximo != null) eventos.add(new Evento("SALIDA4", proximo.darT1()+proximo.darTiempoEnCola()+proximo.darT2(), proximo));
        }
    }
    
}
